﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Xml;

namespace Netfraction.ShareManager.Hashing
{
    public class HashEventArgs : EventArgs
    {
        private FileInfo fileInfo;
        private HashTree hashTree;

        public HashEventArgs(FileInfo fileInfo, HashTree hashTree)
        {
            this.fileInfo = fileInfo;
            this.hashTree = hashTree;
        }

        public FileInfo FileInfo
        {
            get { return fileInfo; }
        }

        public HashTree HashTree
        {
            get { return hashTree; }
        }
    }

    public class HashPool
    {
        public event EventHandler<HashEventArgs> FileHashed;
        //Dictionary<string, FileInfo> hashFileInfos;

        private static HashAlgorithm algorithm;
        private const int MaxLevels = 10;    // ADC1.0 says keep at least 7, DC++ HashManager.cpp hides this in the call to calcBlockSize()
        private const int MinBlockSize = 64 * 1024; // mBlock granularity of 64kb
        private static long bytesRemaining;

        private ActionQueue<string> mActionQueue;

        public ActionQueue<string> ActionQueue
        {
            get { return mActionQueue; }
            set { mActionQueue = value; }
        }

        public HashPool(HashAlgorithm hashAlg)
        {
            //hashFileInfos = new Dictionary<string, FileInfo>();
            algorithm = hashAlg;

            mActionQueue = new ActionQueue<string>("Hashing Thread");
        }

        /// <summary>
        /// Adds a file to the hash queue.
        /// </summary>
        /// <parameter name="fileInfo">The file to be hashed.</parameter>
        public void HashFile(FileInfo fileInfo)
        {
            if (fileInfo == null)
            {
                throw new ArgumentNullException("fileInfo");
            }
            //lock (files)
            //{
                if (mActionQueue.Contains(fileInfo.FullName))
                {
                    return;
                }
                Console.WriteLine("Queue: {0}", fileInfo.Name);
                mActionQueue.Queue(fileInfo.FullName, HashFile, fileInfo.FullName);
                //files.Add(fileInfo.FullName, fileInfo.Length);

                bytesRemaining += fileInfo.Length;
            //}
        }
        
        private void HashFile(object state)
        {
           /* FileInfo f = state as FileInfo;
            Console.WriteLine("Start Time: {0}", DateTime.Now.ToLongTimeString());

            HashTree hashTree = CreateHashTree(f);

            Console.WriteLine("End Time: {0}", DateTime.Now.ToLongTimeString());
            OnFileHashed(f, hashTree);*/
            string filename = (string)state;
            /*lock (files)
            {
                long length;
                if (files.TryGetValue(filename, out length))
                {
                    bytesRemaining -= length;
                    files.Remove(filename);
                }
            }*/
            try
            {
                FileInfo fileInfo = new FileInfo(filename);
                //log.Info("Hashing " + fileInfo.FullName);
                System.Diagnostics.Debug.WriteLine(String.Format("Hashing: {0}", fileInfo.FullName));

                HashTree hashTree = CreateHashTree(fileInfo, mActionQueue.AbortEvent);

                // The hashing was not aborted
                if (!mActionQueue.AbortEvent.WaitOne(0, false))
                {
                    OnFileHashed(fileInfo, hashTree);
                }
            }
            catch (FileNotFoundException)
            {
            }
            catch (Exception e)
            {
                //log.Error("Hashing failed", e);
                System.Diagnostics.Debug.WriteLine(String.Format("Hashing: {0}, Exception: {1}", filename, e));
            }
        }

        private HashTree CreateHashTree(FileInfo fileInfo, ManualResetEvent abortHashingEvent)
        {
            long blockSize = HashTree.CalculateBlockSize(fileInfo.Length, MaxLevels);
            blockSize = Math.Max(blockSize, MinBlockSize);

            HashTree hashTree = new HashTree(algorithm, blockSize);

            using (FileStream stream = fileInfo.OpenRead())
            {
                bytesRemaining = stream.Length;

                int count;
                byte[] buffer = new byte[HashTree.SegmentSize * 8];
                do
                {
                    count = stream.Read(buffer, 0, buffer.Length);
                    if (count != 0)
                    {
                        hashTree.Update(buffer, 0, count);
                        bytesRemaining -= count;
                    }
                }
                while (count != 0 && !abortHashingEvent.WaitOne(0, false));
            }

            // The hashing was aborted
            if (bytesRemaining > 0 || abortHashingEvent.WaitOne(0, false))
            {
                bytesRemaining = 0;
                return null;
            }

            hashTree.CalculateRoot();
            return hashTree;
        }

        /// <summary>
        /// Emits the FileHashed event.
        /// </summary>
        /// <parameter name="fileInfo">The file that's been hashed.</parameter>
        /// <parameter name="hashTree">The resulting hash tree.</parameter>
        protected virtual void OnFileHashed(FileInfo fileInfo, HashTree hashTree)
        {
            if (FileHashed != null)
            {
                FileHashed(this, new HashEventArgs(fileInfo, hashTree));
            }
        }
    }
}
